package com.fetech.melon.idfactory.impl;

import com.fetech.melon.context.log.LogUtil;
import com.fetech.melon.context.utils.map.MapUtils;
import com.fetech.melon.dao.mongodb.MongoClient;
import com.fetech.melon.dao.mongodb.Order;
import com.fetech.melon.idfactory.exception.UidGenerateException;
import com.fetech.melon.idfactory.increment.IncrementLong;
import com.fetech.melon.idfactory.increment.IncrementLongService;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by ZhangGang on 2017/9/29.
 */
@Service
public class IncrementLongServiceImpl implements IncrementLongService {

    private Map<String, AtomicLong> idsMap = null;
    private static final long start = 0;
    private Lock lock;

    @Resource
    private MongoClient mongoClient;

    private static final String collection = "com.fetech.melon.idfactory.increment.IncrementLong";

    @PostConstruct
    public void init() {
        idsMap = new HashMap<>();
        lock = new ReentrantLock();
    }

    @Override
    public void init(String key, long start) {
        try {
            lock.lock();
            update2Mongodb(key, start);
            idsMap.put(key, new AtomicLong(start));
        } catch (Exception e) {
            LogUtil.error(e);
            throw new UidGenerateException(e);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public Long getNewId(String key) {
        AtomicLong atomicLong;
        long id;
        try {
            lock.lock();
            atomicLong = idsMap.get(key);
            if (null == atomicLong) {
                IncrementLong aLong = findIdFromMongoByKey(key);
                long curId = null == aLong ? start : aLong.getCurId();
                atomicLong = new AtomicLong(curId);
                idsMap.put(key, atomicLong);
            }
            id = atomicLong.incrementAndGet();
            //异步 TODO
            update2Mongodb(key, id);
        } catch (Exception e) {
            LogUtil.error(e);
            throw new UidGenerateException(e);
        } finally {
            lock.unlock();
        }
        return id;
    }

    private IncrementLong findIdFromMongoByKey(String key) {
        return mongoClient.findByOne(MapUtils.map("key", key).get(), Order.by("curId", false), IncrementLong.class, collection);
    }

    private void update2Mongodb(String key, long curId) {
        IncrementLong incrementLong = findIdFromMongoByKey(key);
        if (null != incrementLong) {
            if (incrementLong.getCurId() > curId) {
                return;
            }
            incrementLong.setCurId(curId);
        } else {
            incrementLong = new IncrementLong();
            incrementLong.setKey(key);
            incrementLong.setCurId(curId);
        }
        mongoClient.save(incrementLong, collection);
    }

    @Override
    public Set<String> getKeys() {
        return idsMap.keySet();
    }

}
